﻿using System;
using System.Collections.Generic;
using ARD.EventRouting;
using ARD.ServiceManagement;
using SharpObjects.SearchEngine.Common.ApplicationEvents;
using SharpObjects.SearchEngine.Common.QueryConstraints;
using SharpObjects.SearchEngine.Common.Search;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Index;

namespace SharpObjects.SearchEngine.Search
{
    public class QueryFactory
    {
        public Lucene.Net.Analysis.Analyzer Analyzer { get; set; }
        public float SimilarityVectorBoost { get; set; }
        private EventBus eventBus;

        public QueryFactory()
        {
            this.SimilarityVectorBoost = 2.0f;
            this.eventBus = ServiceManager.Instance.GetService<EventBus>("default_event_bus");
        }

        public Query CreateQuery(IQueryConstraint constraint)
        {
            Query query = null;

            if(constraint is TermQueryConstraint)
            {
                query = CreateTermQuery((TermQueryConstraint) constraint);
            }
            else if(constraint is PhraseQueryConstraint)
            {
                query = CreatePhraseQuery((PhraseQueryConstraint) constraint);
            }
            else if(constraint is SimilarVectorQueryConstraint)
            {
                SimilarVectorQueryConstraint similarVectorQueryConstraint = (SimilarVectorQueryConstraint) constraint;

                BooleanQuery boolQuery = new BooleanQuery();
                query = boolQuery;

                foreach (TermVectorItem item in similarVectorQueryConstraint.Vector)
                {
                    TermQuery termQuery = new TermQuery(new Term(similarVectorQueryConstraint.FieldName, item.Term));
                    termQuery.SetBoost((float)item.Score * this.SimilarityVectorBoost); // score is normalized - boost less than 1 is not really a boost, so we need to multiply by something
                    boolQuery.Add(termQuery, BooleanClause.Occur.SHOULD);
                }
            }
            else if(constraint is SimilarDocumentQueryConstraint)
            {
                SimilarDocumentQueryConstraint similarDocumentQueryConstraint = (SimilarDocumentQueryConstraint)constraint;

                BooleanQuery masterQuery = new BooleanQuery();
                query = masterQuery;

                foreach(string fieldName in similarDocumentQueryConstraint.FieldBoosts.Keys)
                {
                    float fieldBoost = (float)similarDocumentQueryConstraint.FieldBoosts[fieldName];

                    GetCharacterizerApplicationEvent getCharacterizerApplicationEvent = new GetCharacterizerApplicationEvent() { Field = fieldName, Uri = similarDocumentQueryConstraint.Uri };
                    this.eventBus.Publish(getCharacterizerApplicationEvent.GetType(), getCharacterizerApplicationEvent);

                    BooleanQuery fieldQuery = new BooleanQuery();
                    masterQuery.Add(fieldQuery, BooleanClause.Occur.SHOULD);

                    foreach (string term in getCharacterizerApplicationEvent.Characterizer.Keys)
                    {
                        double score = getCharacterizerApplicationEvent.Characterizer[term];

                        TermQuery termQuery = new TermQuery(new Term(fieldName, term));
                        termQuery.SetBoost((float)score * fieldBoost);
                        fieldQuery.Add(termQuery, BooleanClause.Occur.SHOULD);
                    }
                }
            }
            else if(constraint is TextQueryConstraint)
            {
                TextQueryConstraint textQueryConstraint = (TextQueryConstraint) constraint;
                QueryParser parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, textQueryConstraint.Text, this.Analyzer);
                query = parser.Parse(textQueryConstraint.Text);
            }
            else if(constraint is GroupQueryConstraint)
            {
                GroupQueryConstraint groupQueryConstraint = (GroupQueryConstraint) constraint;
                BooleanQuery booleanQuery = new BooleanQuery();
                BooleanClause.Occur logic;

                if(groupQueryConstraint.Logic == GroupQueryConstraint.eQueryLogic.AND)
                {
                    logic = BooleanClause.Occur.MUST;
                }
                else
                {
                    logic = BooleanClause.Occur.SHOULD;
                }

                foreach (IQueryConstraint queryConstraint in groupQueryConstraint.Constraints)
                {
                    booleanQuery.Add(CreateQuery(queryConstraint), logic);
                }
            }
            else
            {
                throw new ApplicationException("Unsupported query constraint: " + constraint.GetType().FullName);
            }

            return query;
        }

        protected TermQuery CreateTermQuery(TermQueryConstraint termConstraint)
        {
            Term term = new Term(termConstraint.FieldName, termConstraint.TermText);
            TermQuery termQuery = new TermQuery(term);
            return termQuery;
        }

        protected PhraseQuery CreatePhraseQuery(PhraseQueryConstraint phraseQueryConstraint)
        {
            PhraseQuery phraseQuery = new PhraseQuery();
            phraseQuery.SetSlop(phraseQueryConstraint.Slop);

            foreach (string termString in phraseQueryConstraint.PhraseTerms)
            {
                Term term = new Term(phraseQueryConstraint.FieldName, termString);
                phraseQuery.Add(term);
            }

            return phraseQuery;
        }

    }
}
